package com.crois.barrier.web.service.impl;

import com.crois.barrier.common.constants.Constants;
import com.crois.barrier.common.dto.ApiParameterInfo;
import com.crois.barrier.common.dto.ApiRouteInfo;
import com.crois.barrier.plugin.BarrierPluginChain;
import com.crois.barrier.web.service.ParamMappingService;
import com.zhimydou.ztools.collection.CollectionUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Hou Ze Yu
 * @description 默认 参数映射 服务
 * @date 2020/10/13
 */
public class DefaultParamMappingService implements ParamMappingService {


    private static final String DEFAULT_LEFT_BRACKET = "{";

    private static final String DEFAULT_RIGHT_BRACKET = "}";

    @Override
    public Mono<Void> doParamMapping(ApiRouteInfo apiRouteInfo, ServerWebExchange exchange, BarrierPluginChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(request) {

            @Override
            public RequestPath getPath() {

                //       /user/{name}/{age}   /user/aaa/dddd
                String servicePath = apiRouteInfo.getServicePath();
                List<ApiParameterInfo> apiParameterInfoList = apiRouteInfo.getApiParameter();
                String requestPathDb = apiRouteInfo.getRequestPath();
                RequestPath requestPath = super.getPath();
                String requestPathString = requestPath.toString();
                Map<String, String> pathParameter = parsePathParameter(requestPathString, requestPathDb);

                URI requestUri = exchange.getRequest().getURI();
                MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
                HttpHeaders requestHeaders = exchange.getRequest().getHeaders();
                MultiValueMap<String, String> afterQuery = getQueryParams();
                URI newUri =  buildNewUri(apiRouteInfo.getEnterParameterModel(),requestUri,requestPathDb,requestPathString,servicePath,apiParameterInfoList,queryParams,requestHeaders,pathParameter,afterQuery);
                return RequestPath.parse(newUri,requestPath.contextPath().value());
            }


            @Override
            public MultiValueMap<String, String> getQueryParams() {
                // 如果 是 透传 模式 直接 用 原来 参数
                if (apiRouteInfo.getEnterParameterModel().equals(Constants.PARAMETER_TRANSPARENT_MAPPING_MODEL)){
                    return super.getQueryParams();
                }else {
                    MultiValueMap<String, String> newQueryParams = new LinkedMultiValueMap<>();
                    MultiValueMap<String, String> queryParams = super.getQueryParams();
                    List<ApiParameterInfo> apiParameterInfoList = apiRouteInfo.getApiParameter();
                    if (CollectionUtils.isNotEmpty(apiParameterInfoList)){
                        for (ApiParameterInfo apiParameterInfo : apiParameterInfoList) {
                            if (apiParameterInfo.getPositionType().equals(Constants.API_PARAMETER_INFO_POSITION_TYPE)
//                                &&apiParameterInfo.getParameterPosition().equals(Constants.QUERY_PARAMETER_INFO_POSITION)
                                    &&apiParameterInfo.getAfterParameterPosition().equals(Constants.QUERY_PARAMETER_INFO_POSITION)) {
                                if (apiParameterInfo.getParameterPosition().equals(Constants.QUERY_PARAMETER_INFO_POSITION)){

                                    String parameterName = apiParameterInfo.getParameterName();
                                    String afterParameterName = apiParameterInfo.getAfterParameterName();
                                    List<String> parameterNameList = queryParams.get(parameterName);
                                    if (CollectionUtils.isNotEmpty(parameterNameList)){
                                        String parameterNameValue = parameterNameList.get(0);
                                        List<String> paramNameList = Arrays.asList(parameterNameValue);
                                        newQueryParams.put(afterParameterName,paramNameList);
                                    }
                                }else if (apiParameterInfo.getParameterPosition().equals(Constants.PATH_PARAMETER_INFO_POSITION)){
                                    String requestPathDb = apiRouteInfo.getRequestPath();
                                    RequestPath requestPath = super.getPath();
                                    String requestPathString = requestPath.toString();
                                    Map<String, String> pathParameter = parsePathParameter(requestPathString, requestPathDb);
                                    String parameterName = apiParameterInfo.getParameterName();
                                    String afterParameterName = apiParameterInfo.getAfterParameterName();
                                    String parameterNameValue =pathParameter.get(parameterName) ;
                                    List<String> paramNameList = Arrays.asList(parameterNameValue);
                                    newQueryParams.put(afterParameterName,paramNameList);
                                }else if (apiParameterInfo.getParameterPosition().equals(Constants.HEADER_PARAMETER_INFO_POSITION)){
                                    HttpHeaders headers = super.getHeaders();
                                    String parameterName = apiParameterInfo.getParameterName();
                                    String afterParameterName = apiParameterInfo.getAfterParameterName();
                                    List<String> parameterNameList = headers.get(parameterName);
                                    if (CollectionUtils.isNotEmpty(parameterNameList)){
                                        String parameterNameValue = parameterNameList.get(0);
                                        List<String> paramNameList = Arrays.asList(parameterNameValue);
                                        newQueryParams.put(afterParameterName,paramNameList);
                                    }
                                }

                            }
                        }
                        if (apiRouteInfo.getEnterParameterModel().equals(Constants.PARAMETER_FILTER_MAPPING_MODEL)){
                            List<String> ParameterNameList = apiParameterInfoList.stream().map(ApiParameterInfo::getParameterName).collect(Collectors.toList());
                            // 判断是否 需要 过滤 参数
                            Set<String> queryParamsSet = queryParams.keySet();
                            Set<String> newQueryParamsSet =new HashSet<>(queryParamsSet);
                            Set<String> newQueryParamSetDb = newQueryParams.keySet();
                            newQueryParamsSet.removeAll(newQueryParamSetDb);
                            newQueryParamsSet.removeAll(ParameterNameList);
                            if (CollectionUtils.isNotEmpty(newQueryParamsSet)){
                                for (String param : newQueryParamsSet) {
                                    List<String> paramValueList = queryParams.get(param);
                                    newQueryParams.put(param,paramValueList);
                                }
                            }
                        }
                    }else {
                        return super.getQueryParams();
                    }
                    return newQueryParams;
                }
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return super.getBody();
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders  newHttpHeaders =new HttpHeaders();
                // 如果 是 透传 模式 直接 用 原来 参数
                if (apiRouteInfo.getEnterParameterModel().equals(Constants.PARAMETER_TRANSPARENT_MAPPING_MODEL)){
                    return super.getHeaders();
                }else if (apiRouteInfo.getEnterParameterModel().equals(Constants.PARAMETER_FILTER_MAPPING_MODEL)){
                    // 过滤未知参数
                    HttpHeaders headers = super.getHeaders();
                    List<ApiParameterInfo> apiParameterInfoList = apiRouteInfo.getApiParameter();
                    if (CollectionUtils.isNotEmpty(apiParameterInfoList)){
                        newHttpHeaders.putAll(buildNewHeader(headers,apiRouteInfo,super.getQueryParams(),super.getPath()));
                    }
                } else {
                    // 透传未知参数
                    HttpHeaders headers = super.getHeaders();
                    List<ApiParameterInfo> apiParameterInfoList = apiRouteInfo.getApiParameter();
                    if (CollectionUtils.isNotEmpty(apiParameterInfoList)){
                        newHttpHeaders.putAll(buildNewHeader(headers,apiRouteInfo,super.getQueryParams(),super.getPath()));
                    }
                    newHttpHeaders.putAll(super.getHeaders());
                }
                return newHttpHeaders;
            }


        };
        // 使用修改后的ServerHttpRequestDecorator重新生成一个新的ServerWebExchange
        return chain.execute(exchange.mutate().request(decorator).build());
    }

    private HttpHeaders buildNewHeader(HttpHeaders headers, ApiRouteInfo apiRouteInfo, MultiValueMap<String, String> queryParams, RequestPath path) {
        HttpHeaders  newHttpHeaders =new HttpHeaders();
        List<ApiParameterInfo> apiParameterInfoList = apiRouteInfo.getApiParameter();
            for (ApiParameterInfo apiParameterInfo : apiParameterInfoList) {
                if (apiParameterInfo.getPositionType().equals(Constants.API_PARAMETER_INFO_POSITION_TYPE)
                        &&apiParameterInfo.getAfterParameterPosition().equals(Constants.HEADER_PARAMETER_INFO_POSITION)) {
                    if (apiParameterInfo.getParameterPosition().equals(Constants.QUERY_PARAMETER_INFO_POSITION)){
                        String parameterName = apiParameterInfo.getParameterName();
                        String afterParameterName = apiParameterInfo.getAfterParameterName();
                        List<String> parameterNameList = queryParams.get(parameterName);
                        if (CollectionUtils.isNotEmpty(parameterNameList)){
                            String parameterNameValue = parameterNameList.get(0);
                            List<String> paramNameList = Arrays.asList(parameterNameValue);
                            newHttpHeaders.put(afterParameterName,paramNameList);
                        }
                    }else if (apiParameterInfo.getParameterPosition().equals(Constants.PATH_PARAMETER_INFO_POSITION)){
                        String requestPathDb = apiRouteInfo.getRequestPath();
                        RequestPath requestPath = path;
                        String requestPathString = requestPath.toString();
                        Map<String, String> pathParameter = parsePathParameter(requestPathString, requestPathDb);
                        String parameterName = apiParameterInfo.getParameterName();
                        String afterParameterName = apiParameterInfo.getAfterParameterName();
                        String parameterNameValue =pathParameter.get(parameterName) ;
                        List<String> paramNameList = Arrays.asList(parameterNameValue);
                        newHttpHeaders.put(afterParameterName,paramNameList);
                    }else if (apiParameterInfo.getParameterPosition().equals(Constants.HEADER_PARAMETER_INFO_POSITION)){
                        String headerParameterName = apiParameterInfo.getParameterName();
                        String afterHeaderParameterName = apiParameterInfo.getAfterParameterName();
                        List<String> parameterNameList = headers.get(headerParameterName);
                        if (CollectionUtils.isNotEmpty(parameterNameList)){
                            String parameterNameValue = parameterNameList.get(0);
                            List<String> paramNameList = Arrays.asList(parameterNameValue);
                            newHttpHeaders.put(afterHeaderParameterName,paramNameList);
                        }
                    }
                }
            }
        return newHttpHeaders;
    }

    private URI buildNewUri(Integer enterParameterModel, URI requestUri, String requestPathDb, String requestPath, String servicePath, List<ApiParameterInfo> apiParameterInfoList, MultiValueMap<String, String> queryParams, HttpHeaders requestHeaders, Map<String, String> pathParameter, MultiValueMap<String, String> afterQuery) {
        String scheme = requestUri.getScheme();
        String host = requestUri.getHost();
        String port = String.valueOf(requestUri.getPort());
        String url = "";
        String afterPath = "";
        requestPathDb= requestPathDb.replace(DEFAULT_LEFT_BRACKET,"").replace(DEFAULT_RIGHT_BRACKET,"");
        requestPathDb= requestPathDb.replace("/**","");
        servicePath=servicePath.replace(DEFAULT_LEFT_BRACKET,"").replace(DEFAULT_RIGHT_BRACKET,"");
        servicePath= servicePath.replace("/**","");
        if (CollectionUtil.isNotEmpty(apiParameterInfoList)) {
            for (ApiParameterInfo apiParameterInfo : apiParameterInfoList) {
                if (apiParameterInfo.getParameterPosition().equals(Constants.PATH_PARAMETER_INFO_POSITION)&&
                        apiParameterInfo.getAfterParameterPosition().equals(Constants.PATH_PARAMETER_INFO_POSITION)){

                    String pathParameterValue = pathParameter.get(apiParameterInfo.getParameterName());

                    servicePath= servicePath.replace(apiParameterInfo.getAfterParameterName(), pathParameterValue);
                    requestPathDb = requestPathDb.replace(apiParameterInfo.getParameterName(), pathParameterValue);
                }else if (apiParameterInfo.getParameterPosition().equals(Constants.QUERY_PARAMETER_INFO_POSITION)&&
                        apiParameterInfo.getAfterParameterPosition().equals(Constants.PATH_PARAMETER_INFO_POSITION)){
                    List<String> queryParamList = queryParams.get(apiParameterInfo.getParameterName());
                    if (CollectionUtils.isNotEmpty(queryParamList)){
                        String pathParameterValue = queryParamList.get(0);
                        servicePath= servicePath.replace(apiParameterInfo.getAfterParameterName(), pathParameterValue);
                    }
                }else if (apiParameterInfo.getParameterPosition().equals(Constants.HEADER_PARAMETER_INFO_POSITION)&&
                        apiParameterInfo.getAfterParameterPosition().equals(Constants.PATH_PARAMETER_INFO_POSITION)){
                    List<String> headerParamList = requestHeaders.get(apiParameterInfo.getParameterName());
                    if (CollectionUtils.isNotEmpty(headerParamList)){
                        String pathParameterValue = headerParamList.get(0);
                        servicePath= servicePath.replace(apiParameterInfo.getAfterParameterName(), pathParameterValue);
                    }
                }
            }
        }
        String[] split = null;
        if (requestPath.equals(requestPathDb)){
            split =  requestPath.split(requestPathDb);
        }else {
            split =  requestPath.split(requestPathDb+"/");
        }
        if (split.length!=0){
             if (enterParameterModel.equals(Constants.PARAMETER_MAPPING_MODEL)||
                     enterParameterModel.equals(Constants.PARAMETER_TRANSPARENT_MAPPING_MODEL)){
                afterPath = split[1];
             }
        }
        if (requestPath.equals(requestPathDb)){
            url=scheme+"://"+host+":"+port+servicePath+afterPath;
        }else {
            url=scheme+"://"+host+":"+port+servicePath+"/"+afterPath;
        }

        return URI.create(url);
    }

    public Map<String,String> parsePathParameter(String requestPath,String requestPathDb){
        String[] split = requestPath.split("/");
        String[] splitDb = requestPathDb.split("/");
        List<String>  pathElement = null;
        List<String>  pathElementDb = null;
        if (split.length!=0){
            pathElement = Arrays.asList(split);
        }
        if (splitDb.length!=0){
            pathElementDb = Arrays.asList(splitDb);

        }
        Map<String,String> parameterPathMap = new HashMap<>();
        for (int i = 1; i < pathElement.size(); i++) {
            for (int i1 = 1; i1 < pathElementDb.size(); i1++) {
                if (i==i1&&!pathElement.get(i).equals(pathElementDb.get(i1))){
                    parameterPathMap.put(pathElementDb.get(i1).replace(DEFAULT_LEFT_BRACKET, "").replace(DEFAULT_RIGHT_BRACKET,""),pathElement.get(i));
                }
            }
        }
        return parameterPathMap;
    }
}
